#include <string.h>
#include "CifDataLoader.h"

CifDataLoader::CifDataLoader()
{
	cifFile = NULL;
	cifData = NULL;

	patternsCount = 13;
	patterns = new char *[patternsCount];
	patterns[0] = CIF_PATTERN_COMMENT_HASH;
	patterns[1] = CIF_PATTERN_SAVE_FRAME_LINK;
	patterns[2] = CIF_PATTERN_COMPLEX_DATA_VALUE_APOSTROPHE;
	patterns[3] = CIF_PATTERN_COMPLEX_DATA_VALUE_QUOTES;
	patterns[4] = CIF_PATTERN_COMPLEX_DATA_VALUE_OPENING_SQUARE_BRAKET;
	patterns[5] = CIF_PATTERN_COMPLEX_DATA_VALUE_CLOSING_SQUARE_BRAKET;
	patterns[6] = CIF_PATTERN_COMPLEX_DATA_VALUE_SEMICOLON;
	patterns[7] = CIF_PATTERN_DATA_BLOCK;
	patterns[8] = CIF_PATTERN_SAVE_FRAME;
	patterns[9] = CIF_PATTERN_LOOP;
	patterns[10] = CIF_PATTERN_STOP;
	patterns[11] = CIF_PATTERN_GLOBAL;
	patterns[12] = CIF_PATTERN_DATA_ITEM;

	patternPos = 0;
	patternNumber = -1;
	complexDataSymbol = ' ';
	readBuffer = new char[READ_BUFFER_MAX_LENGTH];
	readBufferLen = 0;
	patternBuffer = new char[PATTERN_BUFFER_MAX_LENGTH];
	patternBufferLen = 0;
}

CifDataLoader::~CifDataLoader()
{
	if (patterns != NULL)
	{
		delete(patterns);
	}

	if (cifFile != NULL)
	{
		fclose(cifFile);
		cifFile = NULL;
	}

	if (readBuffer != NULL)
	{
		delete(readBuffer);
	}

	if (patternBuffer != NULL)
	{
		delete(patternBuffer);
	}
}

void CifDataLoader::Parse()
{
	char c;
	int res;

	SkipWhitespacesAndComments();

	while ((c = ReadNextSymbol()) != EOF)
	{
		switch (patternNumber)
		{
		case CIF_PATTERN_NUM_NO:
			break;

		case CIF_PATTERN_NUM_CONTINUE:
			continue;

		default:
			res = RunPatternAction(patternNumber);

			if (res != CIF_ERROR_CODE_OK)
			{
				perror("ERROR!");
				return;
			}
		}
	}
}

int CifDataLoader::IsWhitespace(char c)
{
	/* c == ' ' => space.       */
	/* c == 9   => tabulation.  */
	/* c == 10  => end of line. */
	/* c == 13  => end of line. */
	if (c == ' ' || c == 9 || c == 10 || c == 13)
	{
		return 1;
	}

	return 0;
}

int CifDataLoader::MatchPattern(char c)
{
	if (patternNumber != -1)
	{
		if (patterns[patternNumber][patternPos] == c)
		{
			patternPos++;

			if (patternPos == strlen(patterns[patternNumber]))
			{
				patternPos = 0;
				int tmp = patternNumber;
				patternNumber = -1;
				return tmp;
			}

			return CIF_PATTERN_NUM_CONTINUE;
		}
		else
		{
			patternPos = 0;
			patternNumber = -1;
			return CIF_PATTERN_NUM_NO;
		}
	}

	for (int i = 0; i < patternsCount; i++)
	{
		if (patterns[i][patternPos] == c)
		{
			patternNumber = i;
			patternPos++;

			if (patternPos == strlen(patterns[i]))
			{
				patternPos = 0;
				int tmp = patternNumber;
				patternNumber = -1;
				return tmp;
			}

			return CIF_PATTERN_NUM_CONTINUE;
		}
	}

	return CIF_PATTERN_NUM_NO;
}

int CifDataLoader::DataBlockAction()
{
	CifDataBlock* cdb = new CifDataBlock();

	if (cdb == NULL)
	{
		perror("Cannot initialize new data block.");
		return CIF_ERROR_CODE_INTERNAL_ERROR;
	}

	cifData->DataBlocks->Add(cdb);

	return ReadDataBlockCode();
}

int CifDataLoader::RunPatternAction(int patternNo)
{
	patternNumber = CIF_PATTERN_NUM_NO;

	switch (patternNo)
	{
	case CIF_PATTERN_NUM_DATA_BLOCK:
		return DataBlockAction();

	case CIF_PATTERN_NUM_LOOP:
		return LoopAction();

	case CIF_PATTERN_NUM_DATA_ITEM:
		return DataItemAction();

	default:
		return CIF_ERROR_CODE_INTERNAL_ERROR;
	}
}

int CifDataLoader::ReadDataBlockCode()
{
	int i = 0;
	char c = getc(cifFile);
	//char c = ReadNextSymbol();
	CifDataBlock *currentBlock = cifData->DataBlocks->GetItemAt(cifData->DataBlocks->GetCount() - 1);

	if (IsWhitespace(c))
	{
		perror("The data block code cannot begins with whitespace.");
		return CIF_ERROR_CODE_FILE_SYNTAX_ERROR;
	}

	while (!IsWhitespace(c) && i < CIF_DATA_BLOCK_CODE_MAX_LEN)
	{
		currentBlock->Code[i++] = c;
		c = getc(cifFile);
		//c = ReadNextSymbol();
	}

	if (!IsWhitespace(c))
	{
		perror("Too long code of a data block.");
		return CIF_ERROR_CODE_FILE_SYNTAX_ERROR;
	}

	currentBlock->Code[i] = '\0';

	SkipWhitespacesAndComments();

	return CIF_ERROR_CODE_OK;
}

int CifDataLoader::DataItemAction()
{
	int res;
	char c;

	CifDataBlock *currentBlock = cifData->DataBlocks->GetItemAt(cifData->DataBlocks->GetCount() - 1);

	CifDataItem *cdi = new CifDataItem();

	if (cdi == NULL)
	{
		perror("Cannot initialize new data item.");
		return CIF_ERROR_CODE_INTERNAL_ERROR;
	}

	currentBlock->DataItems->Add(cdi);

	res = ReadDataItemName();

	if (res != CIF_ERROR_CODE_OK)
	{
		perror("Cannot read a name of a data block.");
		return res;
	}

	c = getc(cifFile);

	while (IsWhitespace(c) && c != EOF)
	{
		c = getc(cifFile);
	}

	if (c == EOF)
	{
		perror("Invalid syntax. There is no value of a data item.");
		return CIF_ERROR_CODE_FILE_SYNTAX_ERROR;
	}

	StepBack();

	return ReadDataItemValue();
}

int CifDataLoader::ReadDataItemName()
{
	int i = 0;
	char c = getc(cifFile);
	CifDataBlock *currentBlock = cifData->DataBlocks->GetItemAt(cifData->DataBlocks->GetCount() - 1);
	CifDataItem *currentItem = currentBlock->DataItems->GetItemAt(currentBlock->DataItems->GetCount() - 1);

	if (IsWhitespace(c))
	{
		perror("The data item name cannot begins with whitespace.");
		return CIF_ERROR_CODE_FILE_SYNTAX_ERROR;
	}

	while (!IsWhitespace(c) && i < CIF_DATA_ITEM_NAME_MAX_LEN)
	{
		currentItem->Name[i++] = c;
		c = getc(cifFile);
	}

	if (!IsWhitespace(c))
	{
		perror("Too long name of a data item.");
		return CIF_ERROR_CODE_FILE_SYNTAX_ERROR;
	}

	currentItem->Name[i] = '\0';

	SkipWhitespacesAndComments();

	return CIF_ERROR_CODE_OK;
}

int CifDataLoader::ReadDataItemValue()
{
	int i = 0;
	int isComplex = 0;
	char buf[1024];
	char c = ReadNextSymbol();
	CifDataBlock *currentBlock = cifData->DataBlocks->GetItemAt(cifData->DataBlocks->GetCount() - 1);
	CifDataItem *currentItem = currentBlock->DataItems->GetItemAt(currentBlock->DataItems->GetCount() - 1);

	if (IsComplexDataValuePattern())
	{
		isComplex = 1;
	}

	if (isComplex)
	{
		StepBack();

		char *value = ReadComplexDataValue();

		if (value == NULL)
		{
			perror("Unable to read complex data value.");
			return CIF_ERROR_CODE_FILE_SYNTAX_ERROR;
		}

		int len = strlen(value);
		memcpy(buf, value, len);
		delete[] value;
		i = len;
	}
	else
	{
		if (IsWhitespace(c))
		{
			perror("Whitespace cannot be at the begin of the data item value.");
			return CIF_ERROR_CODE_FILE_SYNTAX_ERROR;
		}

		while (!IsWhitespace(c) && c != EOF)
		{
			buf[i++] = c;
			c = getc(cifFile);
		}
	}

	currentItem->Value = new char[i + 1];
	memcpy(currentItem->Value, buf, i);
	currentItem->Value[i] = '\0';

	SkipWhitespacesAndComments();

	return CIF_ERROR_CODE_OK;
}

int CifDataLoader::SkipLine()
{
	char c = getc(cifFile);

	while (c != 10 && c != 13 && c != EOF)
	{
		c = getc(cifFile);
	}

	/* Unix / Windows end of line. */
	while (c == 10 || c == 13)
	{
		c = getc(cifFile);
	}

	StepBack();

	return CIF_ERROR_CODE_OK;
}

int CifDataLoader::StepBack()
{
	/* We must clear all pattern data because of stepping back. */
	patternNumber = 0;
	patternBufferLen = 0;
	
	fseek(cifFile, ftell(cifFile) - 1, 0);

	return CIF_ERROR_CODE_OK;
}

int CifDataLoader::StepBack(int stepsCount)
{
	/* We must clear all pattern data because of stepping back. */
	patternNumber = 0;
	patternBufferLen = 0;

	fseek(cifFile, ftell(cifFile) - stepsCount, 0);

	return CIF_ERROR_CODE_OK;
}

int CifDataLoader::SkipWhitespacesAndComments()
{
	int fl = 1;
	char c;

	while (fl)
	{
		c = getc(cifFile);

		if (c == EOF)
		{
			return CIF_ERROR_CODE_OK;
		}

		while (IsWhitespace(c))
		{
			c = getc(cifFile);
		}

		if (c != '#')
		{
			fl = 0;
			StepBack();
		}
		else
		{
			SkipLine();
		}
	}

	return CIF_ERROR_CODE_OK;
}

int CifDataLoader::LoopAction()
{
	int res;

	SkipWhitespacesAndComments();

	CifDataTable *cdt = new CifDataTable();
	CifDataBlock *currentBlock = cifData->DataBlocks->GetItemAt(cifData->DataBlocks->GetCount() - 1);
	currentBlock->DataTables->Add(cdt);

	res = ReadLoopColumnNames();

	if (res != CIF_ERROR_CODE_OK)
	{
		perror("Cannot read column names for the loop.");
		return CIF_ERROR_CODE_FILE_SYNTAX_ERROR;
	}

	SkipWhitespacesAndComments();

	return ReadLoopRows();
}

int CifDataLoader::ReadLoopColumnNames()
{
	int i = 0;
	int fl = 1;
	char c;
	char buf[1024];
	CifDataBlock *currentBlock = cifData->DataBlocks->GetItemAt(cifData->DataBlocks->GetCount() - 1);
	CifDataTable *currentTable = currentBlock->DataTables->GetItemAt(currentBlock->DataTables->GetCount() - 1);

	while (fl)
	{
		i = 0;
		c = ReadNextSymbol();

		if (c != '_')
		{
			perror("Got invalid symbol during reading of column name.");
			return CIF_ERROR_CODE_FILE_SYNTAX_ERROR;
		}

		while (!IsWhitespace(c))
		{
			buf[i++] = c;
			c = ReadNextSymbol();
		}

		char *columnName = new char[i];
		memcpy(columnName, buf + 1, i - 1);
		columnName[i - 1] = '\0';

		currentTable->ColumnNames->Add(columnName);

		while (IsWhitespace(c))
		{
			c = ReadNextSymbol();
		}

		if (c != '_')
		{
			fl = 0;
		}

		StepBack();
	}

	return CIF_ERROR_CODE_OK;
}

int CifDataLoader::ReadLoopRows()
{
	int i = 0;
	int fl = 1;
	char c;
	char buf[1024];
	int isComplex = 0;
	CifDataBlock *currentBlock = cifData->DataBlocks->GetItemAt(cifData->DataBlocks->GetCount() - 1);
	CifDataTable *currentTable = currentBlock->DataTables->GetItemAt(currentBlock->DataTables->GetCount() - 1);
	int columnsCount = currentTable->ColumnNames->GetCount();
	List<char *> *currentRow = new List<char *>();
	int valuesCount = 0;

	while (fl)
	{
		i = 0;
		isComplex = 0;
		c = ReadNextSymbol();

		if (IsComplexDataValuePattern())
		{
			isComplex = 1;
		}
		else if (patternNumber >= 0)
		{
			if (valuesCount != 0)
			{
				/* It seems that we have a lack of values for the current row. */
				perror("Have found matched pattern before read all columns of the row.");
				return CIF_ERROR_CODE_FILE_SYNTAX_ERROR;
			}

			StepBack();

			return CIF_ERROR_CODE_OK;
		}

		if (isComplex)
		{
			StepBack();

			char *value = ReadComplexDataValue();

			if (value == NULL)
			{
				perror("Unable to read complex data value.");
				return CIF_ERROR_CODE_FILE_SYNTAX_ERROR;
			}

			int len = strlen(value);
			memcpy(buf, value, len);
			delete[] value;
			i = len;
		}
		else
		{
			if (IsWhitespace(c))
			{
				if (valuesCount == 0)
				{
					return CIF_ERROR_CODE_OK;
				}
				else
				{
					perror("Whitespace cannot be at the begin of the data item value.");
					return CIF_ERROR_CODE_FILE_SYNTAX_ERROR;
				}
			}

			while (!IsWhitespace(c) && c != EOF)
			{
				buf[i++] = c;
				c = ReadNextSymbol();

				if (patternNumber >= 0 && valuesCount == 0)
				{
					StepBack(i + 1);

					return CIF_ERROR_CODE_OK;
				}
			}
		}

		char *columnValue = new char[i + 1];
		memcpy(columnValue, buf, i);
		columnValue[i] = '\0';

		currentRow->Add(columnValue);
		valuesCount++;

		SkipWhitespacesAndComments();

		if (valuesCount == columnsCount)
		{
			valuesCount = 0;
			currentTable->Rows->Add(currentRow);
			currentRow = new List<char *>();
		}
	}

	return CIF_ERROR_CODE_OK;
}

char CifDataLoader::ReadNextSymbol()
{
	char c = getc(cifFile);
	int equalCount = 0;
	int matchedPattern = 0;
	int fullMatch = 0;

	if (c == EOF)
	{
		return c;
	}

	patternBuffer[patternBufferLen++] = c;

	for (int i = 0; i < patternsCount; i++)
	{
		fullMatch = 1;

		for (int j = 0; j < patternBufferLen; j++)
		{
			if (patternBuffer[j] != patterns[i][j])
			{
				fullMatch = 0;
			}
		}

		if (fullMatch)
		{
			matchedPattern = i;
			equalCount++;
		}
	}

	if (equalCount == 0)
	{
		patternNumber = CIF_PATTERN_NUM_NO;
		patternBufferLen = 0;
	}
	else if (equalCount == 1)
	{
		if (patternBufferLen == strlen(patterns[matchedPattern]))
		{
			patternNumber = matchedPattern;
			patternBufferLen = 0;
		}
		else
		{
			patternNumber = CIF_PATTERN_NUM_CONTINUE;
		}
		
	}
	else
	{
		patternNumber = CIF_PATTERN_NUM_CONTINUE;
	}

	return c;
}

int CifDataLoader::IsPatternMatched()
{
	if (patternNumber >= 0)
	{
		return 1;
	}

	return 0;
}

int CifDataLoader::IsComplexDataValuePattern()
{
	if (patternNumber == CIF_PATTERN_NUM_COMPLEX_DATA_VALUE_APOSTROPHE ||
		patternNumber == CIF_PATTERN_NUM_COMPLEX_DATA_VALUE_QUOTES ||
		patternNumber == CIF_PATTERN_NUM_COMPLEX_DATA_VALUE_OPENING_SQUARE_BRAKET ||
		patternNumber == CIF_PATTERN_NUM_COMPLEX_DATA_VALUE_CLOSING_SQUARE_BRAKET ||
		patternNumber == CIF_PATTERN_NUM_COMPLEX_DATA_VALUE_SEMICOLON)
	{
		return 1;
	}

	return 0;
}

char *CifDataLoader::ReadComplexDataValue()
{
	int i = 0;
	int flag = 1;
	int isOneLine = 0;
	int isSpecialSemicolon = 0;
	char *value = NULL;
	char buf[1024];
	char c = ReadNextSymbol();

	if (c == EOF)
	{
		perror("Got EOF instead of complex data value opening character.");
		return NULL;
	}

	if (c == CIF_PATTERN_COMPLEX_DATA_VALUE_APOSTROPHE[0] ||
		c == CIF_PATTERN_COMPLEX_DATA_VALUE_QUOTES[0])
	{
		complexDataValueOpeningCharacter = c;
		complexDataValueClosingCharacter = c;
		isOneLine = 1;
	}
	else if (c == CIF_PATTERN_COMPLEX_DATA_VALUE_SEMICOLON[0])
	{
		complexDataValueOpeningCharacter = c;
		complexDataValueClosingCharacter = c;

		c = ReadNextSymbol();
		if (c == '\\')
		{
			c = ReadNextSymbol();
			if (c == 10 || c == 13)
			{
				while (c == 10 || c == 13)
				{
					c = ReadNextSymbol();
				}

				isSpecialSemicolon = 1;
			}
			else
			{
				StepBack();
			}
		}
		else if (c == 10 || c == 13)
		{
			while (c == 10 || c == 13)
			{
				c = ReadNextSymbol();
			}
			
			StepBack();
		}
		else
		{
			StepBack();
		}
		
	}
	else if (c == CIF_PATTERN_COMPLEX_DATA_VALUE_OPENING_SQUARE_BRAKET[0])
	{
		complexDataValueOpeningCharacter = c;
		complexDataValueClosingCharacter = CIF_PATTERN_COMPLEX_DATA_VALUE_CLOSING_SQUARE_BRAKET[0];
	}
	else
	{
		perror("A complex data value must begin with one of opening characters.");
		return NULL;
	}
	
	while (flag)
	{
		c = ReadNextSymbol();

		if (c == EOF)
		{
			perror("Got EOF instead of data in complex data value.");
			return NULL;
		}

		if (c == complexDataValueClosingCharacter)
		{
			if (isOneLine)
			{
				c = ReadNextSymbol();
				
				if (IsWhitespace(c))
				{
					while (IsWhitespace(c))
					{
						c = ReadNextSymbol();
					}

					StepBack();
					break;
				}
				else
				{
					StepBack(2);
					c = ReadNextSymbol();
				}
			}
			else
			{
				StepBack(2);
				char prev = ReadNextSymbol();
				ReadNextSymbol();
				char next = ReadNextSymbol();

				if ((prev == 10 || prev == 13) && next == 10 || next == 13)
				{
					while (c == 10 || c == 13)
					{
						c = ReadNextSymbol();
					}

					StepBack();
					break;
				}
			}
		}

		/* Replace end of line with the whitespace or just skip it. */
		if (c == 10 || c == 13)
		{
			while (c == 10 || c == 13)
			{
				c = ReadNextSymbol();
			}

			if (!isSpecialSemicolon)
			{
				buf[i++] = ' ';
			}

			if (c == complexDataValueClosingCharacter)
			{
				if (isOneLine)
				{
					c = ReadNextSymbol();

					if (c == 10 || c == 13)
					{
						while (c == 10 || c == 13)
						{
							c = ReadNextSymbol();
						}

						StepBack();
						break;
					}
					else
					{
						StepBack(2);
						c = ReadNextSymbol();
					}
				}
				else
				{
					StepBack(2);
					char prev = ReadNextSymbol();
					ReadNextSymbol();
					char next = ReadNextSymbol();

					if ((prev == 10 || prev == 13) && next == 10 || next == 13)
					{
						while (c == 10 || c == 13)
						{
							c = ReadNextSymbol();
						}

						StepBack();
						break;
					}
				}
			}
		}

		buf[i++] = c;
	}


	value = new char[i + 1];
	memcpy(value, buf, i);
	value[i] = '\0';

	return value;
}